﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Platform.Linq
{

	/// <summary>
	/// Conjunto de funções que realizam o mapeamento do que será agrupado/calculado
	/// </summary>
	/// <typeparam name="TInput"></typeparam>
	/// <typeparam name="TKey"></typeparam>
	/// <typeparam name="TEmit"></typeparam>
	public sealed class MapperFunction<TInput, TKey, TEmit>
	{
		private Expression<Func<TInput, TEmit>> emit;
		private Expression<Func<TInput, TEmit>> emitNegated;
		private Expression<Func<TEmit, TKey>> keySelector;
		private Func<TInput, TEmit> emitCompiled;
		private Func<TInput, TEmit> emitNegatedCompiled;
		private Func<TEmit, TKey> keySelectorCompiled;


		public MapperFunction( Expression<Func<TInput, TEmit>> emitter, Expression<Func<TEmit, TKey>> keySelector )
		{
			this.emit = emitter;
			this.keySelector = keySelector;

			this.emitCompiled = null;
			this.keySelectorCompiled = null;
		}

		public Expression<Func<TInput, TEmit>> Emit
		{
			get { return this.emit; }
		}

		public Expression<Func<TInput, TEmit>> EmitNegated
		{ get { return this.emitNegated; } }

		public Expression<Func<TEmit, TKey>> KeySelector
		{
			get { return this.keySelector; }
		}

		public Func<TInput, TEmit> EmitCompiled
		{
			get
			{
				if ( this.emitCompiled == null )
					this.emitCompiled = this.Emit.Compile();
				return this.emitCompiled;
			}
		}

		public Func<TInput, TEmit> EmitNegatedCompiled
		{
			get
			{
				if ( this.emitNegatedCompiled == null )
					this.emitNegatedCompiled = this.emitNegated.Compile();
				return this.emitNegatedCompiled;
			}
		}

		public Func<TEmit, TKey> KeySelectorCompiled
		{
			get
			{
				if ( this.keySelectorCompiled == null )
					this.keySelectorCompiled = this.keySelector.Compile();
				return this.keySelectorCompiled;
			}
		}

		internal void SetEmitNegated()
		{
			this.SetEmitNegated( null );
		}

		internal void SetEmitNegated( Expression<Func<TInput, TEmit>> emitter )
		{
			if ( emitter == null )
			{
				// TODO : Cria uma novo emitter, baseado em "this.emit", só que invertendo o sinal dos valores númericos e ignorando os campos que são chaves
				// throw new NotImplementedException();
				var visitor = new AutoNegateExpressionVisitor();
				emitter = visitor.Negate( this.Emit, this.KeySelector );
			}

			this.emitNegated = emitter;
			this.emitNegatedCompiled = null;
		}


		internal sealed class AutoNegateExpressionVisitor : ExpressionVisitor
		{

			public Expression<Func<TEmit, TKey>> KeySelector
			{ get; private set; }

			public Expression<Func<TInput, TEmit>> Negate( Expression<Func<TInput, TEmit>> mapper, Expression<Func<TEmit, TKey>> keySelector )
			{
				this.KeySelector = KeySelector;
				return ( Expression<Func<TInput, TEmit>> )base.Visit( mapper );
			}


			protected override MemberAssignment VisitMemberAssignment( MemberAssignment node )
			{
				return base.VisitMemberAssignment( node );
			}

            protected override Expression VisitConstant( ConstantExpression node )
            {
                switch ( Convert.GetTypeCode( node.Value ) )
                {
                    case TypeCode.Boolean:
                        return Expression.Constant( !Convert.ToBoolean( node.Value ) );
                    case TypeCode.Decimal:
                        return Expression.Constant( Decimal.Negate( ( decimal )node.Value ) );
                    case TypeCode.Double:
                        return Expression.Constant( ( double )-1 * ( double )node.Value );
                    case TypeCode.Int16:
                        return Expression.Constant( ( short )-1 * ( short )node.Value );
                    case TypeCode.Int32:
                        return Expression.Constant( ( int )-1 * ( int )node.Value );
                    case TypeCode.Int64:
                        return Expression.Constant( ( long )-1 * ( long )node.Value );
                    case TypeCode.SByte:
                        return Expression.Constant( ( sbyte )-1 * ( sbyte )node.Value );
                    case TypeCode.Single:
                        return Expression.Constant( ( float )-1 * ( float )node.Value );
                }
                return base.VisitConstant( node );
            }
		}
	}
}
